import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Random;

import com.martiansoftware.jsap.*;
import GTGExpession.*;
import bexpred.*;

/**
 * @author Tomasz Werszko
 * @author Rafal Magda
 *
 */
public class GTG {
	
	/*
	 * variables storing execution options
	 */
	private static JSAP jsap;
	private static JSAPResult COMMANDS;
	
	private static void registerParameters(String[] args) throws JSAPException{
		GTG.jsap = new JSAP();
		Switch sw1 = new Switch("rm")
		 						.setShortFlag('r')
		 						.setLongFlag("reed-muller");
		sw1.setHelp("Prints the expresson in Reed-Muller form.");
	    jsap.registerParameter(sw1);
	    
	    Switch sw2 = new Switch("sop")
								.setShortFlag('s')
								.setLongFlag("sum-of-products");
	    sw2.setHelp("If the expression is reducable prints reduced expression as a sum of products.");
	    jsap.registerParameter(sw2);
	    
	    Switch sw3 = new Switch("pos")
								.setShortFlag('p')
								.setLongFlag("product-of-sums");
	    sw3.setHelp("If the expression is reducable prints reduced expression as a product of sums.");
	    jsap.registerParameter(sw3);
	    
	    Switch sw4 = new Switch("synthesis")
								.setShortFlag('z')
								.setLongFlag("synthesis");
	    sw4.setHelp("Synthesizes GTG circuit.");
	    jsap.registerParameter(sw4);
	    
	    Switch sw5 = new Switch("tt")
								.setShortFlag('t')
								.setLongFlag("truth-table");
	    sw5.setHelp("Prints truth table of the expression.");
	    jsap.registerParameter(sw5);
	    
	    Switch sw6 = new Switch("help")
								.setShortFlag('h')
								.setLongFlag("help");
	    sw6.setHelp("Prints help.");
	    jsap.registerParameter(sw6);
	    
	    Switch sw7 = new Switch("verbose")
								.setShortFlag('v')
								.setLongFlag("verbose");
	    sw7.setHelp("Prints more information about generation process.");
	    jsap.registerParameter(sw7);
	    
		Switch sw8 = new Switch("time")
								.setShortFlag('c')
								.setDefault("false")
								.setLongFlag("time");
		sw8.setHelp("Prints sysnthesis algorithm time.");
		jsap.registerParameter(sw8);
		
		Switch sw9 = new Switch("iterations")
								.setShortFlag('i')
								.setDefault("false")
								.setLongFlag("iterations");
		sw9.setHelp("Prints number of synthesis algorithm iterations.");
		jsap.registerParameter(sw9);
		
		Switch sw10 = new Switch("branches")
								.setShortFlag('b')
								.setDefault("false")
								.setLongFlag("branches");
		sw10.setHelp("Prints number of branches needed to realize GTG circuit.");
		jsap.registerParameter(sw10);		
		
		Switch sw11 = new Switch("algorithm")
								.setShortFlag('a')
								.setDefault("false")
								.setLongFlag("algorithm");
		sw11.setHelp("Prints GTG expression synthesis algorithm.");
		jsap.registerParameter(sw11);	
	    
	    FlaggedOption opt1 = new FlaggedOption("expression")
        										.setStringParser(JSAP.STRING_PARSER)
        										.setDefault("") 
        										.setRequired(true) 
        										.setShortFlag('e') 
        										.setLongFlag("expression");
	    String tmp="Operation priorities are (from the highest to the lowest): "
	    	+"NOT ( ~ )\n"
	    	+"AND ( * )\n"
	    	+"OR ( + ) , XOR ( ^ ) - the same priority (only order matters)\n"
	    	+"You can use brackets - () to change priorities\n"
	    	+"You MUST add quotation mark - \" at the beginning and at the end of the expression.\n";
	    opt1.setHelp(tmp); 
	    jsap.registerParameter(opt1);
	    
        FlaggedOption opt2 = new FlaggedOption("tests")
        										.setStringParser(JSAP.INTEGER_PARSER)
        										.setDefault(JSAP.NO_DEFAULT)
        										.setRequired(false)
        										.setListSeparator(',')
        										.setList(true)
        										.setShortFlag(JSAP.NO_SHORTFLAG) 
        										.setLongFlag("tests");
     opt2.setHelp("Makes automatic tests. Requires 3 intiger parameters:\n"+
    		 		"number of variables,\n"+
        			"numer of tests,\n"+
        			"0 - for \"pseudo R-M\" expressions, 1 - for normal expressions.\n"+
        			"Generates output in .csv format.\n"+
        			"No other parameters are considered, except --verbose to print progress in error output.\n"+
        			"Example: --tests 5,100,0");
        jsap.registerParameter(opt2);

	    COMMANDS = jsap.parse(args);
	        
	    // check whether the command line was valid, and if it wasn't,
	    // display usage information and exit.
	    if (!COMMANDS.success()) {
	    	GTG.printHelp();
	        System.exit(1);
	    }
	}
	
	/**
	 * Prints help
	 */
	private static void printHelp(){
		System.err.println();
        System.err.println("Usage: java -jar gtg.jar");// + GTG.class.getName());
        System.err.println("                " + jsap.getUsage());
        System.err.println();
        System.err.println(GTG.jsap.getHelp());
        System.err.println("Authors: Rafal Magda (rafalmag@gmail.com)");
        System.err.println("         Tomasz Werszko (twerszko@gmail.com)");
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		//parsing command line arguments
		try{
			registerParameters(args);
		}catch(JSAPException ex){
			System.exit(1);
		}
		
		//printing help
		if(COMMANDS.getBoolean("help") || args.length == 0){
			GTG.printHelp();
			System.exit(1);
		}
		if(COMMANDS.contains("tests")){
			testUsage();
		}else{
			normalUsage();
		}
	}
	
	private static void testUsage() {
		int[] tmp = COMMANDS.getIntArray("tests");
		if(tmp.length !=3){
			GTG.printHelp();
			System.exit(1);
		}
		int variables = tmp[0];
		if(variables<=0){
			return;
		}
		int iterations = tmp[1];
		boolean RMexpression=false;
		if(tmp[2]==0){
			RMexpression=true;
		}else if(tmp[2]==1){
			RMexpression=false;
		}else{
			GTG.printHelp();
			System.exit(1);
		}
		
		boolean verbose =false;
		if(COMMANDS.getBoolean("verbose")){
			verbose=true;
		}
		
			
		System.out.println("no. \tIN expression \tR-M expression \tOUT expression \tvariables in R-M\tR-M conversion time \tSynthesis time \tCheck time \tSynthesis iterations \tBranches \tCheck status");
		
		for(int i=0; i< iterations ; i++){
			if(verbose){
				System.err.println((i+1)+"/"+iterations);
			}
			// no.
			System.out.print((i+1)+"\t");
			long t1=0,t2=0,t3=0;
			String expression;
			if(RMexpression){
				expression = randomRMExpression(variables, variables);
			}else{
				expression = randomExpression(variables, 1, 2);
			}
			// IN expression
			System.out.print(expression+"\t");
			
			// R-M conversion 
		    GTGExpression gtgExpr;
		    try{
		    	long t1a = System.currentTimeMillis();
		    	gtgExpr = new GTGExpression(expression);
				long t1b = System.currentTimeMillis();
				t1=t1b-t1a;
		    }catch(BExprException ex){
		    	System.out.println("ERROR");
		    	System.err.println(ex.getMessage());
		    	//ex.printStackTrace();
		    	continue;
		    }
			
		    String inputRM = gtgExpr.toStringW1();

    		long t2a = System.currentTimeMillis();
	    	gtgExpr.synthesize(-1);	// -1 = do not print NDR_l < NDR_d or NDR_l > NDR_d
			long t2b = System.currentTimeMillis();
			
			// R-M expression
			System.out.print(inputRM+"\t");
			// OUT expression
			System.out.print(gtgExpr.toString()+"\t");
			
			//variables in R-M
			if(gtgExpr.count()!=0){
				BExprTree bexprtree;
				try {
			    	bexprtree = new BExprTree(inputRM);
			    	System.out.print(bexprtree.getVarCount()+"\t");
			    }catch (BExprPreParseException ex) {
			    }
		    }else{
		    	System.out.print("0\t");
		    }
		    	
			//System.out.print(variablesInRM+"\t");
			
			// R-M conversion time
			System.out.print(t1+"\t");
			
			t2=t2b-t2a;
			// Synthesis time
			System.out.print(t2+"\t");
				    
		    try {
	    		// checking
	    		boolean equal=false;
	    		long t3a = System.currentTimeMillis();
	    		String gtg = gtgExpr.toString();
	    		if(inputRM.equals(gtg)){
	    			equal=true;
	    		}else{
					BExprTree bet_expr= new BExprTree(inputRM);
					BExprTree bet_expr2= new BExprTree(gtg);
					equal=bet_expr.compareExprTo(bet_expr2);
	    		}
				long t3b = System.currentTimeMillis();
				// Check time
				t3=t3b-t3a;
				System.out.print(t3+"\t");
				
				// Synthesis iterations
				System.out.print(gtgExpr.k+"\t");
				
				// Branches
				System.out.print(gtgExpr.count()+"\t");
				
				if (equal==true){
					System.out.print("OK");
				}else{
					System.out.print("ERROR : NO MATCH");
				}
			} catch (BExprPreParseException e) {
				System.out.println("ERROR");
				System.err.println(e.getMessage());
				//e.printStackTrace();
				continue;
			}
			System.out.println();
		}	// iterations for
	}

	private static void normalUsage(){
		if(COMMANDS.getBoolean("algorithm")){
			System.out.print("Synthesis algorithm:\n" +
					"1: Y (X) = Sort(Y (X)),\n"+
					"2: set i = 1, and j = 2,\n"+
					"3: if N_i(X)*N_j(X) != N_k(X) for k = i, j, then\n"+
					"4.1: set N_i(X)   N_i(X) + N_j(X),\n"+
					"4.2: set N_j(X)   N_i(X)N_j(X),\n"+
					"4.3: simplify Y(X),\n"+
					"4.4: Y (X) = Sort(Y (X)),\n"+
					"   else\n"+
					"5: set j = j + 1,\n"+
					"   end if\n"+
					"6: if j > Count(Y (X)) then i = i + 1, j = i + 1,\n"+
					"7: if i < Count(Y (X)) then goto 3 step\n");
		}
		
    	int verbose = 0;
    	if(COMMANDS.getBoolean("verbose")){
    		verbose = 1;
    	}
    	
    	boolean time = false;
    	long t1=0,t2=0,t3=0;
    	if(COMMANDS.getBoolean("time")){
    		time = true;
    	}
		
		String expression = null;
		if(COMMANDS.getString("expression").equals("")){
			expression = inputExpression();
		}else{
			expression = COMMANDS.getString("expression");
		}
		
	    //building  expression
	    GTGExpression gtgExpr;
	    try{
	    	if(COMMANDS.getBoolean("synthesis")){
	    		System.out.println("\n1. Converting to R-M form.");
	    	}
	    	long t1a = System.currentTimeMillis();
	    	gtgExpr = new GTGExpression(expression);
			long t1b = System.currentTimeMillis();
			if(time){
				t1=t1b-t1a;
				System.out.println("time = "+t1+" ms");
			}
		
	    }catch(BExprException ex){
	    	gtgExpr = null;
	    	System.out.println(ex.getMessage());
	    	ex.printStackTrace();
	    }
	    
	    //printing truth table
	    if(COMMANDS.getBoolean("tt")){
	    	gtgExpr.printTruthTable();
	    }
	    
	    //printing R-M form
	    if(COMMANDS.getBoolean("rm") || COMMANDS.getBoolean("synthesis")){
	    	System.out.println("R-M expression:");
		    System.out.println(gtgExpr.RMtoString());
		    //System.out.println(gtgExpr.toString());
	    }
	    
	    //printing SOP
	    if(COMMANDS.getBoolean("sop")){
	    	System.out.println("Sum of products:");
	    	System.out.println(GTGExpression.SOPReducerToString(expression));
	    }
	    
	    //printing POS
	    if(COMMANDS.getBoolean("pos")){
	    	System.out.println("Product of sums:");
	    	System.out.println(GTGExpression.POSReducerToString(expression));
	    }
	    
	    //synthesis
	    if(COMMANDS.getBoolean("synthesis")){
    		System.out.println("\n2. Synthesize R-M to GTG.");
    		String inputRM = gtgExpr.toStringW1();
    		long t2a = System.currentTimeMillis();
	    	gtgExpr.synthesize(verbose);
			long t2b = System.currentTimeMillis();
			if(COMMANDS.getBoolean("iterations"))
				System.out.println("Iterations: "+gtgExpr.k);
			if(time){
				t2=t2b-t2a;
				System.out.println("time = "+t2+" ms");
			}
			if(COMMANDS.getBoolean("branches")){
				System.out.println("Branches: "+gtgExpr.count());
			}
		    //System.out.print("Output= ");
		    //System.out.println(gtgExpr.toString());
	    	System.out.println("Output: ");
		    System.out.print(gtgExpr.toOutputString());
		    
		    try {
	    		System.out.println("\n3. Checking if input equals output.");
	    		boolean equal=false;
	    		long t3a = System.currentTimeMillis();
	    		String gtg = gtgExpr.toString();
	    		if(inputRM.equals(gtg)){
	    			equal=true;
	    		}else{
					BExprTree bet_expr= new BExprTree(inputRM);
					BExprTree bet_expr2= new BExprTree(gtg);
					equal=bet_expr.compareExprTo(bet_expr2);
	    		}
				long t3b = System.currentTimeMillis();
				if(time){
					t3=t3b-t3a;
					System.out.println("time = "+t3+" ms");
				}
				if (equal==true){
					System.out.println("Input expression equals to output expression. Congratulations!");
				}else{
					System.out.println("Input expression does NOT equal to output expression. Synthesis error!");
				}
			} catch (BExprPreParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if(time){
				System.out.println("Total algorithms time = "+(t1+t2+t3)+" ms");
			}
	    }
	}

	private static String inputExpression() {
		//input
		System.out.print("Enter boolean expression: ");
		
		//  open up standard input
	    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	    
	    try {
	    	String expression = br.readLine();
	    	return expression;
	    } catch (IOException ioe) {
	    	System.out.println("IO error trying to read the expression!");
	    	System.exit(1);
	    }
	    return null;
	}
	
	/**
	 * 
	 * @param variables - how many different variables
	 * @param depth - one level of brackets
	 * @param endCondition - minimum value, which one variable has to be used in one level of expression to end
	 * @return random expression
	 */
	private static String randomExpression(int variables, int depth, int endCondition){
		Random generator = new Random();
		int [] varCount = new int[variables];
		String expression = new String();
		
		//should we generate operation ?
		boolean operation =  false;
		while(true){
			//operation
			if(operation){		
				int op = generator.nextInt(3);
				//+
				if(op == 0){
					expression += "+";
				}
				//*
				else if(op == 1){
					expression += "*";
				}
				//^
				else{
					expression += "^";
				}
				operation = false;
			}
			
			//number or bracket
			// 75% variable , 25% bracket
			int entity = generator.nextInt(4);	//0 - 3 - number; 4 - bracket
			//number
			if(entity < 3){
				//~ or not
				int sign = generator.nextInt(2);	//0 - nothing happens; 1 - adding "~"
				if(sign == 1){
					expression += "~";
				}
				int variable = generator.nextInt(variables);
				varCount[variable]++;
				expression += "x"+(variable+1);
				//is the end condition fulfilled ?
				boolean limit = false;
				boolean unused = false;
				// TODO make new variable for "depth+1"
				for(int i = 0; i < variables; i++){
					if(varCount[i] >= endCondition){
						limit = true;
					}else if(varCount[i] == 0){
						unused = true;
					}
				}
				if(limit && !unused){
					return expression;
				}
				operation = true;
			}
			//bracket
			else{
				if(depth > 0){
					//~ or not
					int sign = generator.nextInt(2);	//0 - nothing happens; 1 - adding "~"
					if(sign == 1){
						expression += "~";
					}
					expression += "(";
					expression += randomExpression(variables, depth - 1, endCondition - 1);
					expression += ")";
					operation = true;
				}
			}			
		}
	}
	/**
	 * 
	 * @param variables - how many different variables
	 * @param endCondition - minimum value, which one variable has to be used in one level of expression to end
	 * @return random expression
	 */
	private static String randomRMExpression(int variables, int endCondition){
		Random generator = new Random();
		int [] varCount = new int[variables];
		String expression = new String();
		
		//should we generate operation ?
		boolean operation =  false;
		while(true){
			//operation
			if(operation){		
				int op = generator.nextInt(3);
				//* 66%
				if(op < 2){
					expression += "*";
				}
				//^ 33%
				else{
					expression += "^";
				}
				operation = false;
			}
			
			//number
			int variable = generator.nextInt(variables);
			varCount[variable]++;
			expression += "x"+(variable+1);
			//is the end condition fulfilled ?
			boolean limit = false;
			boolean unused = false;
			for(int i = 0; i < variables; i++){
				if(varCount[i] >= endCondition){
					limit = true;
				}else if(varCount[i] == 0){
					unused = true;
				}
			}
			if(limit && !unused){
				return expression;
			}
			operation = true;
		}
	}
}
